package com.ruoyi.dingtalk.controller;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.dingtalk.common.CommonValue;
import com.ruoyi.dingtalk.ienum.EventSubscribeType;
import com.ruoyi.dingtalk.security.DingCallbackCrypto;
import com.ruoyi.dingtalk.service.DdDeptService;
import com.ruoyi.dingtalk.service.DdUserService;
import com.ruoyi.dingtalk.service.DingTalkApiService;
import com.ruoyi.dingtalk.util.DdConfigSign;
import com.ruoyi.system.service.ISysConfigService;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

@Api(value = "钉钉控制器", tags = "钉钉控制器")
@Slf4j
@RestController
@RequestMapping(value = "/dingTalk")
public class DingTalkController extends BaseController {
    private final Map<String, Consumer<JSONObject>> eventMethodMap = new HashMap<>();
    public ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private DdDeptService ddDeptService;
    private DdUserService ddUserService;
    private RedisCache redisCache;
    private ISysConfigService iSysConfigService;
    private DingTalkApiService dingTalkApiService;

    @Autowired
    public void setDingTalkApiService(DingTalkApiService dingTalkApiService) {
        this.dingTalkApiService = dingTalkApiService;
    }

    @Autowired
    public void setRedisCache(RedisCache redisCache) {
        this.redisCache = redisCache;
    }


    @Autowired
    public void setDdDeptService(DdDeptService ddDeptService) {
        this.ddDeptService = ddDeptService;
    }

    @Autowired
    public void setDdUserService(DdUserService ddUserService) {
        this.ddUserService = ddUserService;
    }

    @Autowired
    @Qualifier(value = "threadPoolTaskExecutor")
    public void setThreadPoolTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }

    @Autowired
    public void setiSysConfigService(ISysConfigService iSysConfigService) {
        this.iSysConfigService = iSysConfigService;
    }

    @PostConstruct
    private void initMethod() {
        //检查连通性
        eventMethodMap.put(EventSubscribeType.CHECK_URL, (s) -> log.info("检查连通性-----> {}", s));
    }


    @ApiOperation(value = "钉钉回调接口", httpMethod = "POST")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(name = "请求体-密文", value = "{}", paramType = "body")
    })
    @Anonymous
    @RequestMapping(value = "/callback")
    public Map<String, String> callBack(@ApiParam(value = "签名串") @RequestParam(value = "msg_signature", required = false) String msgSignature,
                                        @ApiParam(value = "时间戳") @RequestParam(value = "timestamp", required = false) String timeStamp,
                                        @ApiParam(value = "随机串") @RequestParam(value = "nonce", required = false) String nonce,
                                        @ApiParam(value = "请求体-密文") @RequestBody(required = false) JSONObject json) {
        try {
            // 1. 从http请求中获取加解密参数
            // 2. 使用加解密类型
            String aesKey = iSysConfigService.selectConfigByKey(CommonValue.DD_CALL_BACK_AES_KEY_CONFIG_KEY);
            String subToken = iSysConfigService.selectConfigByKey(CommonValue.DD_CALL_BACK_TOKEN_KEY_CONFIG_KEY);
            String corpId = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_KEY_CONFIG_KEY);
            if (log.isDebugEnabled()) {
                log.debug("aesKey : {}", aesKey);
                log.debug("subToken : {}", subToken);
                log.debug("corpId : {}", corpId);
            }
            if (StringUtils.isAnyBlank(aesKey, subToken, corpId)) {
                log.error("钉钉DingCallbackCrypto构造参数获取错误!");
            } else {
                DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(subToken, aesKey, corpId);
                if (Objects.nonNull(json)) {
                    String encryptMsg = json.getString("encrypt");
                    String decryptMsg = callbackCrypto.getDecryptMsg(msgSignature, timeStamp, nonce, encryptMsg);
                    // 3. 反序列化回调事件json数据
                    JSONObject eventJson = JSON.parseObject(decryptMsg);
                    try {
                        performLogical(eventJson);
                    } catch (Exception e) {
                        log.error("执行钉钉回调逻辑出现异常:", e);
                    }
                }
                // 5. 返回success的加密数据
                return callbackCrypto.getEncryptedMap("success");
            }
        } catch (Exception e) {
            log.error("error:", e);
        }
        return null;
    }

    /**
     * 执行回调逻辑
     *
     * @param eventJson 回调数据
     */
    private void performLogical(JSONObject eventJson) {
        String eventType = eventJson.getString("EventType");
        if (log.isDebugEnabled()) {
            log.debug("发生了:{}事件", eventType);
        }
        // 根据 eventType  事件分类 回调进行处理
        if (eventMethodMap.containsKey(eventType)) {
            eventMethodMap.get(eventType).accept(eventJson);
            if (log.isDebugEnabled()) {
                log.debug("相关事件:{},执行完毕!", eventType);
            }
        } else {
            log.warn("未找到相关回调事件方法:{}", eventType);
        }
    }

    /**
     * 生成钉钉微应用的鉴权签名
     *
     * @param url 前端当前网页的URL，不包含#及其后面部分。
     * @return AjaxResult
     * @throws Exception Exception
     */
    @ApiOperation(value = "生成JS鉴权签名", httpMethod = "GET")
    @Anonymous
    @RequestMapping(value = "/jsSign")
    public AjaxResult dingJsSign(@ApiParam(value = "需要签名的url") @RequestParam(value = "url") String url) throws Exception {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(url)) {
            return AjaxResult.error("请求参数错误");
        }
        String appKey = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_KEY_CONFIG_KEY);
        String appSecret = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_SECRET_CONFIG_KEY);
        String jsapiTicket = dingTalkApiService.getJsapiTicket(appKey, dingTalkApiService.getAccessToken(appKey, appSecret));
        //随机字符串
        String nonceStr = DdConfigSign.getRandomStr(16);
        //应用表述
        String agentId = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_AGENT_ID_CONFIG_KEY);
        //企业ID
        String corpId = iSysConfigService.selectConfigByKey(CommonValue.DD_CROP_ID_CONFIG_KEY);
        String signStr = DdConfigSign.sign(jsapiTicket, nonceStr, System.currentTimeMillis(), url);
        Map<String, Object> resultMap = new HashMap<>(16);
        resultMap.put("agentId", agentId);
        resultMap.put("corpId", corpId);
        resultMap.put("signStr", signStr);
        return AjaxResult.success("success", resultMap);
    }


    /**
     * 同步钉钉用户信息
     *
     * @return Ajax
     */
    @ApiOperation(value = "同步钉钉用户信息")
    @PreAuthorize("@ss.hasPermi('system:dingtalk:syncuser')")
    @RequestMapping(value = "/syncDingTalkOrgUser", method = RequestMethod.GET)
    public AjaxResult syncDingTalkDept() {
        if (log.isDebugEnabled()) {
            log.debug("开始同步钉钉部门用户信息!");
        }
        Boolean a = redisCache.hasKey("syncdingtalkuser");
        if (a) {
            return AjaxResult.error("钉钉组织架构正在同步中,请勿重复点击!");
        }
        synchronized (this) {
            Boolean b = redisCache.hasKey("syncdingtalkuser");
            if (b) {
                return AjaxResult.error("钉钉组织架构正在同步中,请勿重复点击!");
            }
            redisCache.setCacheObject("syncdingtalkuser", StringUtils.EMPTY);
            SecurityContext securityContext = SecurityUtils.getContext();
            threadPoolTaskExecutor.execute(() -> {
                //跨线程传递安全上下文
                SecurityUtils.setContext(securityContext);
                //用户初始密码
                String initPassword = iSysConfigService.selectConfigByKey("sys.user.initPassword");
                //钉钉企业id
                String ddCropId = iSysConfigService.selectConfigByKey(CommonValue.DD_CROP_ID_CONFIG_KEY);
                //获取钉钉应用授权码
                String appKey = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_KEY_CONFIG_KEY);
                String appSecret = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_SECRET_CONFIG_KEY);
                try {
                    ddDeptService.genDeptUser(1L, 0L, 0L,
                            "0.", initPassword, ddCropId, appKey, appSecret);
                } catch (Exception e) {
                    log.error("同步钉钉组织架构出现异常:", e);
                }
                if (log.isDebugEnabled()) {
                    log.debug("同步钉钉组织架构完毕=============》");
                }
                redisCache.deleteObject("syncdingtalkuser");
            });
        }
        return AjaxResult.success("同步已经开始,请稍等。。。");
    }
}
